Skip to content

test(platform-wallet): e2e framework + scenario suite, upstream bug pins + setup-gate hardening (v54: 98 PASS/10 FAIL)#3549

Draft
Claudius-Maginificent wants to merge 278 commits into
fix/rs-platform-wallet-auto-select-inputsfrom
feat/rs-platform-wallet-e2e
Draft

test(platform-wallet): e2e framework + scenario suite, upstream bug pins + setup-gate hardening (v54: 98 PASS/10 FAIL)#3549
Claudius-Maginificent wants to merge 278 commits into
fix/rs-platform-wallet-auto-select-inputsfrom
feat/rs-platform-wallet-e2e

Conversation

@Claudius-Maginificent
Copy link
Copy Markdown
Collaborator

@Claudius-Maginificent Claudius-Maginificent commented Apr 27, 2026

Issue being fixed or feature implemented

End-to-end test framework and scenario suite for rs-platform-wallet, plus a campaign of e2e-fix commits that pin known upstream behaviors and harden setup gates. Validated by the v54 run: 98 PASS / 10 FAIL — the 10 failures are upstream red-by-design pins plus env-masked cases, not regressions.

What was done?

e2e framework + scenarios live here. This update lands the e2e-fix campaign:

  • CR-004 (63ee3ba443): retarget to the positive feat(wasm-dpp): provide external entropy generator to document factory #845 change-routing proof — BIP-32 UTXO routing pin after spend.
  • PA-003 (d3c02ca20b): pin fee scaling on real chain-time fee with symmetric pre-markers.
  • PA-005b (7651ca8d6a): rebaseline DIP-17 gap-limit triplet to real eager-pool state.
  • PA-009c (f625f830ee): pin via deterministic on-chain read-back (QA-014).
  • TK-001/TK-014 (f78dedad53): harden setup gates against Found-025 sync-discard race via proof-verified wait_for_address_balance_chain_confirmed_n.
  • README (f5ddda61c0, docs c6693df4f2): correct run flag (--include-ignored) and flag al_001 Found-008 env-mask.

Production-code carve-out posture: this branch carries production clusters that have their own dedicated PRs and are NOT owned here:

The e2e framework and scenarios remain in this PR.

How Has This Been Tested?

Full e2e run v54: 98 PASS / 10 FAIL. The 10 failures are accounted for as upstream red-by-design pins plus env-masked cases — no functional regressions introduced by this campaign.

Breaking Changes

None. Test-only and documentation changes; no production API surface modified by this PR.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

@github-actions github-actions Bot added this to the v3.1.0 milestone Apr 27, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e834cafa-2b9e-49ec-b8f8-88c0218ab722

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

An end-to-end testing framework for rs-platform-wallet is added with shared process context, bank wallet funding, persistent wallet registry, cleanup/orphan sweeping, and complete test infrastructure including configuration management, SDK setup, event notification, and example test cases demonstrating transfers between platform addresses. Related SDK modules are also exposed for external use, and seed-based signer constructors are introduced.

Changes

Cohort / File(s) Summary
E2E Framework Infrastructure
packages/rs-platform-wallet/tests/e2e/framework/config.rs, workdir.rs, registry.rs
Environment variable loading with .env file support, workdir slot reservation with exclusive file locking, and persistent JSON-backed wallet seed registry with atomic writes and corruption recovery.
E2E Harness & Context
packages/rs-platform-wallet/tests/e2e/framework/harness.rs, mod.rs
Process-shared E2eContext initialization via OnceCell, SDK/wallet manager/bank/registry/event-hub injection, setup utilities for seed generation and test wallet creation with registry entry insertion.
Bank Wallet & Funding
packages/rs-platform-wallet/tests/e2e/framework/bank.rs
BIP-39 mnemonic-backed bank wallet with minimum-credit validation, DIP-17 address derivation, and concurrent-safe fund transfers using global async mutex to prevent nonce races.
Cleanup & Lifecycle
packages/rs-platform-wallet/tests/e2e/framework/cleanup.rs, wallet_factory.rs
Orphan wallet sweeping at startup with fund drainage and registry status updates, per-test teardown with unregistration, plus test wallet factory with address selection, balance sync, and cleanup guards.
SDK & Event Infrastructure
packages/rs-platform-wallet/tests/e2e/framework/sdk.rs, wait_hub.rs, context_provider.rs
SDK construction with TrustedHttpContextProvider, testnet DAPI endpoint defaults, WaitEventHub for event-driven async test waits, and SpvContextProvider implementing platform SDK context bridge.
Wait & Polling Utilities
packages/rs-platform-wallet/tests/e2e/framework/wait.rs, spv.rs
Generic polling loop with timeout, event-driven balance-change waiter with sync/notification integration, SPV client startup and masternode-list sync readiness checker with progress logging.
E2E Test Cases & Entry Points
packages/rs-platform-wallet/tests/e2e.rs, cases/mod.rs, cases/transfer.rs, README.md, .env.example
Integration test root module, test case organization, fund-and-transfer example test verifying fee deduction, framework documentation with operator setup requirements and architecture overview, and environment configuration template.
Cargo Dependencies
packages/rs-platform-wallet/Cargo.toml
Dev-dependency additions: tokio-shared-rt, tempfile, dotenv, bip39, fs2, parking_lot, simple-signer, SDK context provider, plus tokio-util rt feature.
SDK Public API Exposure
packages/rs-sdk/src/platform/transition.rs, transition/address_inputs.rs
Module visibility change from crate-restricted to public; functions fetch_inputs_with_nonce and nonce_inc exposed for external callers.
Signer Feature & Constructors
packages/simple-signer/Cargo.toml, signer.rs
New derive feature pulling key-wallet and thiserror dependencies; two seed-based constructors for platform-address and identity signers with BIP-32 derivation and error mapping.

Sequence Diagram(s)

sequenceDiagram
    participant Test as E2E Test
    participant Harness as E2eContext Harness
    participant Registry as Wallet Registry
    participant Bank as BankWallet
    participant TWallet as TestWallet
    participant Manager as PlatformWalletManager
    participant SDK as SDK/PlatformWallet
    participant Cleanup as Cleanup

    Test->>Harness: init() first call
    Harness->>Registry: open(test_wallets.json)
    Harness->>Cleanup: sweep_orphans()
    Cleanup->>Registry: list_orphans()
    Cleanup->>Manager: create from orphan seed
    Cleanup->>SDK: sync & drain to bank
    Cleanup->>Registry: remove_orphan_entry
    Harness->>Bank: load from mnemonic
    Harness->>Bank: sync_balances()
    Harness->>Bank: fund_address(test_addr1, credits)
    Harness->>SDK: transfer via bank wallet
    Test->>Test: setup() generates seed
    Test->>Manager: create TestWallet
    Test->>TWallet: create(seed)
    Test->>TWallet: next_unused_address() → addr2
    Test->>Bank: fund_address(addr2, TRANSFER_CREDITS)
    Test->>SDK: transfer via bank
    Test->>TWallet: wait_for_balance(addr2, expected)
    TWallet->>SDK: sync_balances()
    Test->>SDK: transfer(addr2 → addr1, TRANSFER_CREDITS)
    SDK->>SDK: execute, compute fee
    Test->>TWallet: verify balances & fee
    Test->>Test: teardown()
    Test->>Cleanup: teardown_one(test_wallet)
    Cleanup->>TWallet: drain all addresses to bank
    Cleanup->>Registry: remove_entry
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes


🐰 Hopping through the test warren,
New E2E frames make wallets soar-in',
Bank funds and registry keep,
While cleanup sweeps run deep,
SDK paths now public—hooray, no more hide-n'! 🌙✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly describes the main changes: adds an e2e testing framework and test suite for platform-wallet with mentioned upstream bug fixes and setup hardening.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/rs-platform-wallet-e2e

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lklimek lklimek changed the title feat(rs-platform-wallet): integration test framework + first transfer test test(platform-wallet): integration test framework + first transfer test Apr 27, 2026
@lklimek lklimek requested a review from Copilot April 27, 2026 14:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an end-to-end (wallet → SDK → broadcast) integration test harness to rs-platform-wallet and introduces the first live test case (address-funds transfer), alongside a production fix to InputSelection::Auto input selection so generated transitions satisfy protocol structure rules.

Changes:

  • Added a reusable E2E framework under packages/rs-platform-wallet/tests/e2e/ (workdir slot locking, bank wallet, persistent registry, cleanup/sweep, wait hub, signer, SDK wiring).
  • Added the first E2E test case: transferring credits between two platform-payment addresses in a test wallet (ignored by default).
  • Fixed auto_select_inputs in production code to avoid selecting full balances as “input credits”, and added unit tests for the selection logic.

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/rs-platform-wallet/src/wallet/platform_addresses/transfer.rs Fixes auto input selection; adds pure helper + unit tests for selection behavior.
packages/rs-platform-wallet/tests/e2e.rs Adds the integration test crate root and module wiring for the e2e suite.
packages/rs-platform-wallet/tests/e2e/README.md Operator/setup documentation for running live e2e tests.
packages/rs-platform-wallet/tests/e2e/cases/mod.rs Declares e2e test modules.
packages/rs-platform-wallet/tests/e2e/cases/transfer.rs First e2e test exercising funding + self-transfer + teardown.
packages/rs-platform-wallet/tests/e2e/framework/mod.rs Framework public surface (setup, errors, prelude) and module layout.
packages/rs-platform-wallet/tests/e2e/framework/harness.rs E2eContext singleton init: config, workdir locking, SDK, manager, bank, registry, startup sweep.
packages/rs-platform-wallet/tests/e2e/framework/config.rs Env/.env configuration loader for the harness.
packages/rs-platform-wallet/tests/e2e/framework/sdk.rs Constructs dash_sdk::Sdk with TrustedHttpContextProvider and DAPI address resolution.
packages/rs-platform-wallet/tests/e2e/framework/workdir.rs Cross-process workdir slot selection via flock.
packages/rs-platform-wallet/tests/e2e/framework/panic_hook.rs Installs panic hook to cancel background work on panic.
packages/rs-platform-wallet/tests/e2e/framework/wait_hub.rs Notify-based hub bridging wallet/SPV/platform events to async waiters.
packages/rs-platform-wallet/tests/e2e/framework/wait.rs Async waiting helpers (event-driven balance wait + generic polling).
packages/rs-platform-wallet/tests/e2e/framework/signer.rs Seed-backed Signer<PlatformAddress> with eager DIP-17 key cache.
packages/rs-platform-wallet/tests/e2e/framework/wallet_factory.rs Test wallet factory + SetupGuard (panic-safe registry-backed lifecycle).
packages/rs-platform-wallet/tests/e2e/framework/registry.rs JSON-backed persistent registry for panic-safe orphan recovery.
packages/rs-platform-wallet/tests/e2e/framework/cleanup.rs Startup sweep + per-test teardown draining funds back to bank.
packages/rs-platform-wallet/tests/e2e/framework/bank.rs Loads and syncs a pre-funded bank wallet; serialized funding API.
packages/rs-platform-wallet/tests/e2e/framework/context_provider.rs Retained (disabled) SPV-backed SDK context provider module for future re-enable.
packages/rs-platform-wallet/tests/e2e/framework/spv.rs Retained (disabled) SPV startup/readiness helpers for future re-enable.
packages/rs-platform-wallet/Cargo.toml Adds dev-dependencies needed by the e2e harness.
Cargo.lock Locks new/updated dependencies for the added test tooling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/rs-platform-wallet/tests/e2e/framework/cleanup.rs Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/README.md Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/README.md Outdated
Comment thread packages/rs-platform-wallet/tests/e2e.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/platform_addresses/transfer.rs Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/framework/registry.rs Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/framework/registry.rs Outdated
@lklimek lklimek changed the base branch from v3.1-dev to fix/rs-platform-wallet-auto-select-inputs April 28, 2026 07:17
Comment thread packages/rs-platform-wallet/tests/e2e/cases/transfer.rs Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/cases/transfer.rs Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/cases/transfer.rs Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/cases/transfer.rs Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/framework/bank.rs Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/framework/panic_hook.rs Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/framework/sdk.rs Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/framework/wait.rs
Comment thread packages/rs-platform-wallet/tests/e2e/framework/wallet_factory.rs Outdated
Comment thread packages/rs-platform-wallet/tests/e2e/framework/wallet_factory.rs
Comment thread packages/rs-platform-wallet/tests/e2e/framework/signer.rs Outdated
lklimek and others added 17 commits April 29, 2026 14:44
… transfer test

Test runs on the default tokio_shared_rt(shared) runtime without forcing
multi_thread flavor. Confirms the harness works under single-threaded
scenarios.

Resolves PR #3549 thread r-DD2o.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…arget

CodeRabbit caught a critical bug on PR #3554's `select_inputs`: the helper
ensured `Σ inputs.credits == Σ outputs.credits` (the protocol's structural
invariant) but did NOT ensure that the address targeted by
`DeductFromInput(0)` had post-consumption remaining balance >= the
estimated fee.

Worked example from CodeRabbit:
  candidates    = [(addr_a, 20M), (addr_b, 50M)]   // addr_a < addr_b lex
  total_output  = 30M
  fee_strategy  = [DeductFromInput(0)]
  Old result    = {addr_a: 20M, addr_b: 10M}       // Σ matches; addr_a drained
  Drive applies DeductFromInput(0) over inputs sorted by key (BTreeMap order),
  hitting addr_a — whose remaining balance is 0 — so `min(fee, 0) = 0`,
  `fee_fully_covered = false`, validator rejects with
  AddressesNotEnoughFundsError.

The Wave-8 single-input live e2e accidentally avoided this because the
fee target had ~1B credits left over after consumption — multi-input
auto-selected transfers would have hit it on first contact.

This rewrite:

- Phase 1 (unchanged): pick smallest DIP-17-ordered prefix covering
  total_output + estimated_fee.
- Phase 2: identify the fee target = lex-smallest address in the prefix
  (= `BTreeMap` index 0, what `DeductFromInput(0)` will hit per
   `rs-dpp/src/address_funds/fee_strategy/.../v0/mod.rs`).
- Phase 3: consume the *minimum* allowed amount from the fee target
  (`max(min_input_amount, total_output − Σ other balances)`) so it
  retains the most remaining balance for fee deduction. Error out
  with a descriptive AddressOperation if even that minimum leaves
  less than `estimated_fee` remaining.
- Phase 4: distribute the rest of `total_output` across the other
  prefix entries in DIP-17 order.
- Phase 5: defensive invariant checks.

`min_input_amount` is fetched from
`platform_version.dpp.state_transitions.address_funds.min_input_amount`
(currently 100k across v1/v2/v3 of platform-version).

For non-`[DeductFromInput(0)]` fee strategies the helper falls back to
the previous "consume from front" distribution that only enforces the
Σ invariant — none of the wallet's call sites use anything else today.

Tests:
- updated `two_input_selection_trims_only_the_last` →
  `two_input_selection_keeps_fee_headroom_at_index_zero` to assert the
  new distribution AND the headroom invariant.
- updated `fee_only_tail_input_does_not_inflate_input_sum`'s expected
  outputs (the tail is no longer dropped — it absorbs the consumption
  the fee target sheds).
- added `fee_target_keeps_remaining_for_fee_deduction` (CodeRabbit's
  exact scenario, with the headroom invariant as the load-bearing
  assertion).
- added `fee_headroom_violation_errors` (lex-smallest address too
  small to retain headroom → descriptive error rather than transition
  the validator will reject).
- `single_input_oversized_balance_trims_to_output_amount`,
  `insufficient_balance_errors`, `no_candidates_errors` pass unchanged.

`cargo test -p platform-wallet --lib` → 117 / 117 green
`cargo clippy -p platform-wallet --tests -- -D warnings` → clean
`cargo fmt -p platform-wallet --check` → clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ee-headroom bug

Adds `pre_fix_buggy_selector_output_is_rejected_by_protocol_fee_deduction`
to the `select_inputs` test module. Reconstructs the exact `inputs` map
the pre-fix `auto_select_inputs` would have returned for CodeRabbit's
example (candidates (20M, 50M), total_output 30M, `DeductFromInput(0)`),
runs the post-consumption remaining balances through the live dpp
fee-deduction code path, and asserts `fee_fully_covered == false` —
i.e. the protocol rejects it with `AddressesNotEnoughFundsError`.

Distinct from `fee_target_keeps_remaining_for_fee_deduction`, which
asserts the new selector's output meets the headroom invariant. This
reproduction proves the bug at the protocol layer rather than merely
asserting "the new output looks different" — it would have stayed red
without the fix in 9ea9e70.

Verification:
- cargo check --tests -p platform-wallet                 OK
- cargo clippy --tests -p platform-wallet -- -D warnings OK
- cargo fmt -p platform-wallet                           OK
- cargo test -p platform-wallet --lib                    118/118

Co-Authored-By: Claudius the Magnificent <noreply@anthropic.com>
…descending

Internal-only change to `auto_select_inputs`. Candidates were
previously collected in DIP-17 derivation index order; now they
sort by balance descending before being handed to `select_inputs`.

Mirrors the dash-evo-tool allocator
(`src/ui/wallets/send_screen.rs:155-157`). Effects:

- Single largest balance covering `total_output + estimated_fee`
  => 1-input result, no multi-input case, no lex-smallest fee
  headroom logic firing. Common path simplified.
- Multi-input cases (when the largest alone isn't enough) still
  go through the headroom-respecting distribution introduced in
  9ea9e70 — unchanged, still correct.
- No public API change. `transfer()`, `auto_select_inputs`,
  `select_inputs` signatures all identical.

Adds `descending_order_picks_single_largest_when_sufficient` to
the existing test module to lock in the common-path behavior.
Other tests pass candidates directly to `select_inputs` and are
order-agnostic by design — unchanged.

The `fee_headroom_violation_errors` error message now includes
the fee-target address, its balance, required headroom, and
remaining-after-consumption to ease debugging.

Verification:
- cargo check --tests -p platform-wallet                 OK
- cargo clippy --tests -p platform-wallet -- -D warnings OK
- cargo fmt -p platform-wallet                           OK
- cargo test -p platform-wallet --lib                    119/119

Co-Authored-By: Claudius the Magnificent <noreply@anthropic.com>
…egy, retry on Phase 3 fail

Addresses the second wave of review findings on PR #3554:

1. [BLOCKING] Phase 4 distribution no longer produces inputs below
   `min_input_amount`. `auto_select_inputs` now filters candidates
   with `balance < min_input_amount` upfront — they cannot legally
   appear in the inputs map. In Phase 4, when a non-fee-target
   tail entry would consume less than `min_input_amount`, the
   residue rolls back into the fee target's consumption (which has
   surplus headroom by construction). Returns a descriptive error
   if rollback would violate the fee-target headroom invariant.

2. [BLOCKING] `transfer()` rejects unsupported `fee_strategy`
   shapes for `InputSelection::Auto`. Auto-select currently only
   implements protocol-correct logic for `[DeductFromInput(0)]`;
   any other strategy returns `PlatformWalletError::AddressOperation`
   with a clear message redirecting callers to
   `InputSelection::Explicit`. Explicit paths still accept
   arbitrary strategies (caller's responsibility).

3. [BLOCKING] When Phase 3 (`fee_target_min > fee_target_max`) fails
   in `select_inputs`, the algorithm now extends the prefix with the
   next candidate and retries instead of erroring out. Larger
   prefixes may yield a different lex-smallest fee target with
   sufficient headroom. Errors out only when candidates are
   exhausted and no covering prefix is feasible.

4. [SUGGESTION] `select_inputs` returns an early descriptive error
   when `total_output < min_input_amount` — the protocol forbids
   this regardless of input shape, so an explicit error beats the
   internal "should never trip" branch that some callers were
   reaching.

5. [SUGGESTION] Existing selector tests now also build a minimal
   `AddressFundsTransferTransitionV0` and run `validate_structure`,
   asserting protocol-level validity in addition to the
   `Σ inputs == total_output` invariant. Catches future regressions
   without needing a live node.

Coderabbit findings DUuz (#3554), DUu1 (#3554), E5L5 (#3554),
thepastaclaw findings F9fo, GMHz, GMH5, GMH_, F9fv addressed.
Outdated F9fk references the renamed test from before 9ea9e70.
Nitpicks F9fz/GMID/F9f5/GMIH deferred (unreachable / low value).

Verification:
- cargo check --tests -p platform-wallet                  OK
- cargo clippy --tests -p platform-wallet -- -D warnings  OK
- cargo fmt -p platform-wallet                            OK
- cargo test -p platform-wallet --lib                     121/121

Co-Authored-By: Claudius the Magnificent <noreply@anthropic.com>
… work

Apply claudius:coding-best-practices rules: length cap (<=2 preferred,
3 mediocre), present-state only (no Wave/PR-number history), two-tier
(strict for internal, liberal for public API rustdoc).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o-select

Extends transfer() / auto_select_inputs to accept [ReduceOutput(0)] in addition
to [DeductFromInput(0)]. Output 0 absorbs the fee, so input selection skips the
fee-headroom reservation. Σ inputs == Σ outputs invariant preserved via last-
input trim.

5 new tests in auto_select_tests cover happy path, multi-input trim, multi-
output isolation, output-too-small error, and structural validation.

Resolves PR #3549 thread r-aCky's production prerequisite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…om key_wallet

Replace the duplicated DEFAULT_ACCOUNT_INDEX / DEFAULT_KEY_CLASS constants
with a default_platform_payment_account_key() helper that destructures
key_wallet's PlatformPaymentAccountSpec::default(), and pin the const _PUB
values to the same canonical struct's fields. A colocated drift test asserts
PlatformPaymentAccountSpec::default() still matches our pinned constants —
preventing silent drift if upstream defaults change.

WalletAccountCreationOptions::Default is a unit variant (the (account, key_class)
shape lives in the BTreeSet variant, not Default itself), so destructuring
Default directly was not viable. Pinning to PlatformPaymentAccountSpec —
the canonical "what does Default mean for a PlatformPayment account" struct
— is the closest equivalent.

Resolves PR #3549 thread r-aA6u.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t(0)

Pay fees by reducing output 0 instead of deducting from input 0. Simpler
to reason about for test authors — recipients see (requested - fee_share),
no input-side reservation needed.

KNOWN BREAKAGE: the existing transfer_between_two_platform_addresses test
asserts an exact recipient balance and will fail under the new default
(recipient receives 10M - fee_share). Test fixture update is a follow-up.

Also re-aligns import ordering in this file with `cargo fmt --all`
defaults (a minor stray drift from the previous commit).

Resolves PR #3549 thread r-aCky.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dex 0

Sweep-back target uses the bank's address-0 deterministically instead of
advancing the unused-address pool every test run. Avoids accumulation of
empty addresses on the bank wallet across test invocations.

Implementation: derive the DIP-17 platform-payment address at index 0
directly from the bank seed (mirroring simple-signer's derivation logic),
side-stepping the AddressPool's "next unused" cursor that would skip
index 0 once it gets marked used.

Resolves PR #3549 thread r-Jhi_.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…put-from-output

Sweep-to-bank uses ReduceOutput(0) so the bank absorbs the fee from its
incoming sum. Drops SWEEP_FEE_ESTIMATE constant and the multi-input fee
headroom math. Sweep gate is now "if address balance > 0".

Resolves PR #3549 thread r-ZluD.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…noop stubs

Split drain_to_bank into per-source helpers: sweep_platform_addresses
(active), sweep_identities, sweep_core_addresses, sweep_unused_core_asset_locks,
sweep_shielded (all noop with TODOs). teardown_one and sweep_orphans now
walk every source type so future sweep implementations slot in cleanly.

Resolves PR #3549 thread r-Zoq9.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tication_path

`AccountType::IdentityAuthenticationEcdsa { identity_index }` was
removed from `key-wallet` between revs `4c8bec3` and `ea33cbc8`.
Replaced with the new top-level `DerivationPath::identity_authentication_path(
network, KeyDerivationType::ECDSA, identity_index, key_index)` API
(`key-wallet/src/bip32.rs:1115`), which bakes both identity_index and
key_index into the path directly — `key_index` becomes the loop
variable instead of an external `extend([leaf])` step.
…ectly

PR #3549 dedup re-audit (PROJ-001): the local DEFAULT_GAP_LIMIT = 20
shadows key_wallet's canonical pub const DIP17_GAP_LIMIT (rust-dashcore
ea33cbc8: key-wallet/src/gap_limit.rs:26). Drop the local constant and
import the upstream one — drift here would silently de-sync the harness
from key-wallet's own gap policy.
…gh PlatformWallet

PR #3549 dedup re-audit (PROJ-002): derive_platform_address_at_index
was running BIP-32 manually from raw seed bytes. The bank already holds
a PlatformWallet whose Wallet::derive_public_key (key-wallet/src/wallet/
helper.rs:763) does the same thing, so the parallel-derivation surface
was redundant. Take the existing &Arc<PlatformWallet>, call
.state().await.wallet().derive_public_key(&path), hash the result.

Drops the bip39 seed-bytes consumer (the seed bytes are still derived
once for SeedBackedPlatformAddressSigner::new four lines below). Net
removes RootExtendedPrivKey, Secp256k1, PublicKey imports from bank.rs.
…r wrapper

`framework/signer.rs` was a 78-line do-nothing shell around
`SimpleSigner::from_seed_for_platform_address_account`:
- the `Signer<PlatformAddress>` trait impl just delegated to inner;
- `SimpleSigner` already implements that trait directly
  (`packages/simple-signer/src/signer.rs:338`);
- `cached_key_count` and `new_with_gap` had zero callers outside the
  module;
- the only added value was pinning `account=0`/`key_class=0`, which
  collapses to four lines of construction code.

Replace with `framework::make_platform_signer(seed_bytes, network) ->
SimpleSigner` next to the `FrameworkError`/`FrameworkResult` types in
`mod.rs`. The three call sites (`bank.rs`, `wallet_factory.rs`,
`cleanup.rs`) now hold `SimpleSigner` directly and pass it straight to
`PlatformAddressWallet::transfer`. `TestWallet::address_signer()`
returns `&SimpleSigner` for the same reason.
@lklimek
Copy link
Copy Markdown
Contributor

lklimek commented Apr 30, 2026

@coderabbitai review all

lklimek and others added 4 commits May 14, 2026 11:02
… existing FFI code

Core split PlatformWalletError::NoSelectableInputs into three typed
variants (NoSpendableInputs, OnlyOutputAddressesFunded, OnlyDustInputs).
The FFI matcher still referenced the old variant name and failed to
compile.

Widen the dedicated ErrorNoSelectableInputs (=14) mapping to cover all
three new variants so Swift consumers keep the same numeric code and
the typed Display rendering survives as the message. The FFI ABI is
preserved — no renumber, no new code, just a broadened match arm and
refreshed docs/test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…OnlyOutputAddressesFunded

The is_nonce_class_error_rejects_no_selectable_inputs test still
constructed the now-deleted PlatformWalletError::NoSelectableInputs
variant. That variant was split into NoSpendableInputs /
OnlyOutputAddressesFunded / OnlyDustInputs (see commit dc5e611 which
fixed the FFI layer for the same rename). The test target failed to
compile (E0599), blocking the whole --test e2e suite from running.

Retarget the assertion onto OnlyOutputAddressesFunded — the field
shape is the cleanest match to the old NoSelectableInputs{funded_outputs}
and preserves the original semantic intent: an insufficient-funds-shape
error must NOT be classified as nonce-class (retrying would just churn
against the same empty pool).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…b, fix spec drift, file Found-026, link Found-006 to rust-dashcore#762

PA-003 (`green` → `red-real-fail (test-bug)`): marker pre-funding pollutes
`address_funds` rows so the 5-output transfer pays cheap UPDATE per recipient
while the 1-output transfer pays the one-time CREATE. Observed Δfee ≈ 536k
matches one absent create. Drive's chain-time fee at
`validate_fees_of_event/v0/mod.rs:195` drives the cost off real drive ops,
not the static `state_transition_min_fees` floor. The line-235 invariant is
misformulated for the chosen address-derivation strategy.

PA-005b spec drift resolved → truth is `blocked`. Both prior `PASS` claims
(detailed body at line ~534 and the "PR #3609 merged" changelog entry) were
stale: they landed on 2026-05-11 in commit `5c6baabd8f` without re-running
against the QA-002 setup hook that had landed seven days earlier on
2026-05-04 (commit `94902be73b`). Three-way contract mismatch:
QA-002's `consume_platform_address_index_zero` marks index 0 used while the
DIP-17 pool eagerly generates indices `0..=19` in `AddressPool::new`, and
the headroom helper at `framework/gap_limit.rs:188-207` measures
fresh-past-`highest_generated` rather than any-unused-below-ceiling.

PA-008b (`green` → `red-real-fail (concurrency-only)`): full-suite 14-thread
cohort FAILS at the canonical 120s `wait_for_balance` timeout on the first
marker funding; `--test-threads=1` isolation re-run PASSES in 158s.
Suspected race in `PlatformAddressWallet::next_unused_receive_address`
(`platform_addresses/wallet.rs:223-270`) vs concurrent BLAST syncs from
sibling tests.

Found-026 added (P2, MEDIUM, suspected) — `next_unused_receive_address`
pool-cursor bump may not enqueue the address into the BLAST sync provider's
pending set under concurrent load. Symmetric to Found-025 on the rs-sdk side.

Found-006 upstream issue filed: **dashpay/rust-dashcore#762** — Add
`top_up_index` field to `CreditOutputFunding::IdentityTopUp` (DIP-9
conformance gap). Wallet-side TODO in `top_up.rs` updated to reference the
issue; once it lands, drop the `_` prefix on `topup_index` and forward it
through the derivation path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lklimek and others added 5 commits May 14, 2026 13:03
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… amplifier note

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#763

Add tracking-issue cross-link in three places (file-level docstring,
`#[ignore]` reason, in line with the AL-001 / #3641
pattern from commit 27087f8).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#764

Add tracking-issue cross-link in the file-level docstring and the
`#[ignore]` reason. Same pattern as Found-021 / #763 (commit 420250d)
and AL-001 / #3641 (commit 27087f8).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shumkov and others added 2 commits May 14, 2026 15:49
Picks up dashpay/rust-dashcore#756 which adds chainlock-driven
transaction finalization in the wallet layer. Previously,
`WalletInterface` had no `process_chain_lock` method and
`dash-spv`'s `SyncEvent::ChainLockReceived` was emitted but never
consumed, so wallet records were stuck at `TransactionContext::
InBlock(_)` forever even when the network produced a chainlock for
the containing block. The new pin promotes records `InBlock →
InChainLockedBlock` on chainlock arrival and emits a new
`WalletEvent::TransactionsChainlocked` variant carrying the
chainlock proof and per-account net-new finalized txids.

For our `wait_for_proof` poll loop this means the chainlock branch
(`record.context.is_chain_locked()`) actually flips when peers
deliver the chainlock — the iter-4 IS→CL fallback path now resolves
correctly instead of timing out at the secondary 180 s deadline.

The new `WalletEvent` variant forces match-arm coverage in two
sites:

- packages/rs-platform-wallet/src/changeset/core_bridge.rs
  `build_core_changeset` returns `CoreChangeSet::default()` for
  the new variant. The wallet has already mutated the in-memory
  record by the time the event fires (upstream is "mutate-then-
  emit"), and the poll loop reads `record.context.is_chain_locked()`
  directly, so no additional persister projection is needed today.
  A future enhancement could persist `WalletMetadata::
  last_applied_chain_lock` for crash recovery, but that's out of
  scope here.

- packages/rs-platform-wallet/src/wallet/core/balance_handler.rs
  `BalanceUpdateHandler::on_wallet_event` returns early for the
  new variant. Chainlocks promote finality (`InBlock →
  InChainLockedBlock`) without changing UTXO state, so there's no
  balance update to deliver.

Extracted from PR #3634 commit 4184a42 onto feat/rs-platform-wallet-e2e.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… 546 (QA-901)

TRACE re-investigation 2026-05-14 confirmed the earlier deterministic
failure was a test-side dust-threshold mismatch (test assumed 2,730 duffs;
upstream `transaction_builder.rs:294`, rev `5313086…`, uses 546). Headroom
changed from 2,500 → 700; new change range [200, 474] is fully sub-dust
across the observed [226, 500] testnet fee range, so the builder folds it
into the fee and the BIP-32 account is truly drained.

CR-004 reclassified red-by-design (dash-evo-tool#845) → passing-as-regression.
The test now pins the symmetric BIP-32 spent-marking path
(TransactionRouter → ManagedAccountCollection → check_transaction_for_match →
update_utxos) plus the upstream sub-dust fold contract. The dash-evo-tool#845
reference is retained as a historical footnote — the symmetric spent-marking
path was confirmed working; any remaining DET surface lives in dash-evo-tool's
own UI refresh path, outside this suite's contract.

TEST_SPEC.md updates: matrix row, detailed body (Layer 2 description + bug
repro note), changelog entry, post-v47 status section, and counts annotation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lklimek and others added 2 commits May 14, 2026 16:02
Cross-link the newly-filed #3642 from the 5 hard-coded
BIP-44 lookup sites in proof.rs + recovery.rs (TODO comments, no logic
change) and from TEST_SPEC.md (matrix rows + detail front-matter +
changelog entry).

Found-012 and Found-023 unify on the same downstream-only fix: replace
`info.core_wallet.accounts.standard_bip44_accounts.get(&account_index)`
with iteration over `all_funding_accounts()` — no upstream change
required; SPV-side tracking already covers all account types.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(already fixed)

Both pins describe contracts that are already satisfied in HEAD:

- Found-019 (SeedBackedIdentitySigner ECDSA_HASH160 re-hash) — fix
  landed at packages/rs-platform-wallet/tests/e2e/framework/signer.rs:148-154
  in commit 59cba08 (PR #3563). identity_key_lookup branches on
  key_type; ECDSA_HASH160 uses key.data() as-is, no re-hash. Production
  packages/simple-signer/src/signer.rs does NOT have the bug shape
  (different storage models).
- Found-020 (PA-001b output_change_address spec/impl drift) resolved
  via spec realignment in PR #3609 (option a). PA-001b rewritten to
  match implicit-change semantics. The parameter doesn't exist in
  production and isn't planned.

Knowledge preserved in memcan; spec clutter dropped. Total Found-bug
pins 26 → 24.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Claudius-Maginificent Claudius-Maginificent changed the title test(platform-wallet): integration test framework + first transfer test test(platform-wallet): e2e framework + 60-test suite, 5 upstream bug pins May 14, 2026
lklimek and others added 6 commits May 15, 2026 10:43
…routing proof

CR-004 now pins the dash-evo-tool#845 contract directly: a BIP-32 send
leaves an above-dust change UTXO, post-broadcast check_core_transaction
routes it back onto the BIP-32 account (count == 1), and a follow-up
spend consumes exactly that routed-back change and succeeds — proving
the change was tracked back into BIP-32, not orphaned. Module doc
states only the present contract (history narration dropped).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e fee with symmetric pre-markers

PA-003 measures the real chain-time fee (Σ gross outputs −
Σ destination balance deltas, the canonical Σ inputs − Σ outputs under
[ReduceOutput(0)]) for two self-transfers that draw inputs exclusively
from a single source address (InputSelection::Explicit). Every
measured destination — including the 1-output dest_1 — is pre-markered
so both shapes hit address-funds UPDATE storage ops with no one-off
CREATE skew; output count is the sole varied factor. Restored guards:
strict fee_5 > fee_1, sub-linear fee_5 < fee_1*5, and the explicit
FEE_DELTA_CEILING linear-fee-schedule tripwire.

Funding defect fix: the explicit-input map value is the actual input
amount the transition encodes (it must balance Σ outputs and be backed
by the address balance), not a placeholder weight. inputs_1 now uses
OUTPUT_AMOUNT and inputs_5 uses 5×OUTPUT_AMOUNT. addr_src funding
bumped 500M → 700M to cover six MARKER_AMOUNT pre-markers (180M) plus
both measured transfers (50M + 250M) with headroom, so addr_src always
holds ≥ the explicit input amount when each transition is built.

TEST_SPEC.md: PA-003 status flipped to green (table row + body) with a
concise changelog line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…real eager-pool state

Production's DIP-17 platform-payment pool is built by the eager
AddressPool::new, which fills indices 0..=gap_limit-1 at construction
(highest_generated = Some(gap_limit-1)); the QA-002 setup hook then
marks index 0 used. From that real state the batch helper's
fresh-past-highest_generated headroom is highest_used + 1 = 1, so the
triplet's empty-pool full-window premise is unreachable in production.

Rather than suppressing eager fill or changing the (correct) shared
helper math at framework/gap_limit.rs, the test now models a real
wallet that has cycled its first DIP-17 gap window: a test-scoped
open_full_gap_window marks index gap_limit-1 used, shifting the ceiling
up by gap_limit to open a genuine gap_limit-wide fresh window. The same
DIP-17 boundary triad is pinned from the production starting state:

- A: request gap_limit-1 fresh addresses, assert success + all distinct
- B: request gap_limit (boundary), assert success + all distinct
- C: request gap_limit+1, assert GapLimitError::Exceeded with every
  field pinned against the LIVE post-mark watermarks
  (requested, available=gap_limit, gap_limit,
  highest_used=Some(gap_limit-1), highest_generated=Some(gap_limit-1)),
  then a boundary retry proves the rejection did not mutate the pool

Shared helper semantics are unchanged. TEST_SPEC.md PA-005b updated in
all three places (quick-index, body Status, changelog) to the accurate
rebaselined contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ead-back (QA-014)

PA-009 sub-case C's post-teardown observation re-derived the gone test
wallet and trusted its recent-zone sync watermark. A watermark-less
re-derived wallet's sync_balances(AddressSyncConfig{
full_rescan_after_time_s: 0 }) resolves to a recent-zone-only query
that returned 0 for addr_1 even though the sub-min_input dust was
correctly abandoned and never swept — a non-deterministic harness gap
(QA-014), not a production defect. The v53 14-thread run failed at the
re-derive read-back assertion (pa_009_min_input_amount.rs:290,
left: 0, right: 1000) while the cleanup-gate abandon-dust path itself
worked correctly.

Replace the re-derive read-back with a direct proof-verified on-chain
read of addr_1 via wait_for_address_balance_chain_confirmed — the same
AddressInfo::fetch gate the funding step already uses successfully
earlier in the same test. The post-condition now asserts addr_1 still
holds exactly TARGET_RESIDUAL on chain, reading real chain state
instead of a stale re-derived local view.

All three pinned invariants are preserved and strengthened:
(a) below-gate dust abandoned, no sweep transition broadcast (a swept
dust drops the balance to ~0, timing out the gate);
(b) gate == PlatformVersion::latest().dpp.state_transitions.address_funds
.min_input_amount and is positive (sub-cases A/B, untouched);
(c) addr_1 residual remains on chain at exactly TARGET_RESIDUAL.

#[ignore] and #[tokio_shared_rt::test(shared)] retained (network-gated,
the standard for all on-chain e2e cases; suite runs --include-ignored).
TEST_SPEC.md PA-009 references (quick-index, body Status/Scenario,
changelog) updated consistently; no stale QA-014/degenerate drift.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… Found-025 sync-discard race

TK-001 and TK-014 failed in the v53 14-thread run, both timing out in the
SETUP FUNDING gate before any token logic ran (tk_001_token_transfer.rs:67
setup_with_token_and_two_identities; tk_014_token_group_action.rs:109
setup_with_per_identity_funding). In both, bank.fund_address chain-confirmed
the funding (nonce streak 2/2) before the wait, then the rs-sdk address-sync
silently discarded the fetched balance update because the target address was
not yet in pending_addresses — Found-025, amplified by 14-thread concurrency.
Not production defects: transfer/group-action/co-sign code never executed and
siblings (TK-001b/c, TK-009/010/012) were green in the same run.

Root cause in the shared chokepoint framework/mod.rs::setup_with_per_identity_funding:
it gated on wait_for_balance, whose proof-verified hand-off only runs AFTER
the Found-025-poisoned local sync map (balances().get(addr)) first reaches
target — so under Found-025 the proof gate was never reached and the budget
expired in the local-view branch (60-62 polls, no chain-confirmed line).

Fix: observe funding directly via the proof-verified AddressInfo::fetch path
(wait_for_address_balance_chain_confirmed_n, CHAIN_CONFIRMED_CONSECUTIVE_SUCCESSES)
— the same chain-state read the validator walks and the family PA-009c
adopted — bypassing the poisoned map entirely. The existing strong
wait_for_address_known_to_platform gate is unchanged. Only the
funding-observation mechanism changed: no funding amounts, identity counts,
contract publish, propose/co-sign, or token/identity assertions altered.

Deterministic and concurrency-independent, so it hardens the whole
setup-helper blast radius (all 22 TK-*/ID-*/CR-003/DPNS-001 cases routing
through setup_with_per_identity_funding). No new Found-NNN pin and no
upstream issue (Found-025 already owns the root cause). A TK-wave
serialization / worker-pool cap remains a documented fallback only — not
implemented, since the proof-verified read-back structurally bypasses the
poisoned map.

TEST_SPEC.md: TK-001 (quick-index + body) and TK-014 (quick-index + body)
reclassified green -> red-real-fail mirroring TK-007 wording, cross-linked
to Found-025; one changelog entry added. All three references per test are
mutually consistent (no stale green/PASS-in-v47 drift).

Live e2e requires a bank-funded node (yarn start) unavailable in this
environment; verified by inspection + cargo build --tests + cargo clippy
(both clean). Live re-validation deferred to the combined v54 run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ound-008 env-mask

FIX 1 (#475): the e2e README documented the opt-in invocation with
`--ignored`, which runs ONLY the `#[ignore]`-attributed subset (~40/108)
and silently skips owned-fix cases. Corrected to `--include-ignored` so the
full suite runs, with a one-line note explaining why `--ignored` alone is wrong.

FIX 2 (#474, CLAUDE.md infra-blocker rule): added a `// TODO(env):` marker in
al_001 near the Core-funded setup gate and a brief note in TEST_SPEC.md's
AL-001/Found-008 entry recording that the Found-008 pin is env-masked when the
e2e testnet Core L1 bank is depleted (al_001 dies at the setup gate, not at the
designed FinalityTimeout; same depletion also fails cr_003 + id_002b). Funding
address phrased generically — the specific address is being verified separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…of-band)

User will top up the e2e Core L1 bank wallet directly, so the env-mask
documentation is unnecessary. Removes the FIX 2 content added in the previous
commit: the `// TODO(env):` marker in al_001 near the Core-funded setup gate
and the AL-001/Found-008 "pin coverage degraded under Core-bank depletion"
bullet in TEST_SPEC.md. Net effect of this commit pair is README-only — the
`--ignored` -> `--include-ignored` run-flag correction (#475).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Claudius-Maginificent Claudius-Maginificent changed the title test(platform-wallet): e2e framework + 60-test suite, 5 upstream bug pins test(platform-wallet): e2e framework + scenario suite, upstream bug pins + setup-gate hardening (v54: 98 PASS/10 FAIL) May 15, 2026
…ter store-error wallet loss)

register_wallet logs and swallows the registration-round persister
`store` error (manager/wallet_lifecycle.rs:276-282) then inserts the
wallet into self.wallets unconditionally (wallet_lifecycle.rs:347-349).
A successful-looking import leaves no persisted record and vanishes on
the next launch — HIGH-severity silent data loss. Note the asymmetry:
the load_persisted / initialize_from_persisted failure paths in the same
function already roll back and return Err; the registration store does
not.

Deterministic pin (no live network, no concurrency): injects a
StoreFailsPersister whose `store` returns Err while `load`/`flush`
succeed (so the already-correct load_persisted rollback path does not
mask the defect), drives create_wallet_from_seed_bytes through a
mock-SDK manager, and asserts the correct atomic-failure contract — the
call returns Err AND the wallet is absent from wallet_ids(). Fails today
for the real reason; flips green once the registration store is treated
as load-bearing. #[ignore]d — live run deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants